#include "BMD_136.h"

#include	<stdio.h>
//#include <gl/glaux.h>

GLuint		texture[14];
GameController gc;
float backR, backG, backB;
const int defR = 30;
const int defG = 70;
const int defB = 0;

static const int texBOMB = 0;
static const int texBOMBER_N = 1;
static const int texBOMBER_E = 2;
static const int texBOMBER_S = 3;
static const int texBOMBER_W = 4;
static const int texBLOCK = 5;
static const int texBRICK = 6;
static const int texEXPLOSION = 7;
static const int texEXPLOSION_BRICK = 8;
static const int texPupBOMB = 9;
static const int texPupSPEED = 10;
static const int texPupPOWER = 11;
static const int texPupKICK = 12;
static const int texPupDETO = 13;


//OpenGL canvas coordinates start from bottom left.
//bmdEngine coordinates start from top left
//must be careful when using offsets with this
double flip(double x){return -x;}

void draw_texture(const double ti, const double tj, const double dH, const double dV)
{
	glTexCoord2f(1.0, 1.0);
	glVertex2d(ti + dH, flip(tj));	// Top Right
	glTexCoord2f(0.0, 1.0);
	glVertex2d(ti, flip(tj));	// Top Left
	glTexCoord2f(0.0, 0.0);
	glVertex2d(ti, flip(tj) - dV);	// Bottom Left
	glTexCoord2f(1.0, 0.0);	
	glVertex2d(ti + dH, flip(tj) - dV);	// Bottom Right
}

GLuint loadTexture(char * textName)	
{
	/////////////////////////////////////////////
	// NEW! - This function has been completely 
	// rewritten to use FreeImage.
	/////////////////////////////////////////////

    //const char textName[64] = ".\\woodfloor.tga";

	// Get the image file type from FreeImage.
	FREE_IMAGE_FORMAT fifmt = FreeImage_GetFileType(textName, 0);

	// Actually load the image file.
	FIBITMAP *dib = FreeImage_Load(fifmt, textName,0);

	// Now, there is no guarantee that the image file
	// loaded will be GL_RGB, so we force FreeImage to
	// convert the image to GL_RGB.
    dib = FreeImage_ConvertTo24Bits(dib);
	

    if( dib != NULL )
	{
		GLuint textureID;
        glGenTextures( 1, &textureID );
		glBindTexture( GL_TEXTURE_2D, textureID );
		glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		glTexParameteri( GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR );

		
		// This is important to note, FreeImage loads textures in
		// BGR format. Now we could just use the GL_BGR extension
		// But, we will simply swap the B and R components ourselves.
		// Firstly, allocate the new bit data doe the image.
		BYTE *bits = new BYTE[FreeImage_GetWidth(dib) * FreeImage_GetHeight(dib) * 3];

		// get a pointer to FreeImage's data.
		BYTE *pixels = (BYTE*)FreeImage_GetBits(dib);

		// Iterate through the pixels, copying the data
		// from 'pixels' to 'bits' except in RGB format.
		for(unsigned int pix=0; pix<FreeImage_GetWidth(dib) * FreeImage_GetHeight(dib); pix++)
		{
			bits[pix*3+0]=pixels[pix*3+2];
			bits[pix*3+1]=pixels[pix*3+1];
			bits[pix*3+2]=pixels[pix*3+0];

		}
	     
        // The new 'glTexImage2D' function, the prime difference
		// being that it gets the width, height and pixel information
		// from 'bits', which is the RGB pixel data..
		glTexImage2D( GL_TEXTURE_2D, 0, 3, FreeImage_GetWidth(dib), FreeImage_GetHeight(dib), 0,
				GL_RGB, GL_UNSIGNED_BYTE, bits );

		
		// Unload the image.
		// and free the bit data.
		FreeImage_Unload(dib);
		delete bits;

		return textureID;
	}
	return 0;
}


void LoadTextures()
{
	texture[texBOMB] = loadTexture("bmdBomb.bmp");
	texture[texBOMBER_N] = loadTexture("bmdBomberN.bmp");
	texture[texBOMBER_E] = loadTexture("bmdBomberE.bmp");
	texture[texBOMBER_S] = loadTexture("bmdBomberS.bmp");
	texture[texBOMBER_W] = loadTexture("bmdBomberW.bmp");
	texture[texBLOCK] = loadTexture("bmdBlock.bmp");
	texture[texBRICK] = loadTexture("bmdBrick.bmp");
	texture[texEXPLOSION] = loadTexture("bmdExplosion.bmp");
	texture[texEXPLOSION_BRICK] = loadTexture("bmdExplosionBrick.bmp");
	texture[texPupBOMB] = loadTexture("bmdPbomb.bmp");
	texture[texPupSPEED] = loadTexture("bmdPspeed.bmp");
	texture[texPupPOWER] = loadTexture("bmdPpower.bmp");
	texture[texPupKICK] = loadTexture("bmdPkick.bmp");
	texture[texPupDETO] = loadTexture("bmdPdeto.bmp");
}


bmdGLCanvas::bmdGLCanvas(wxFrame* parent, int* args) :
wxGLCanvas(parent, wxID_ANY,  wxPoint(0,0), wxSize(640,480), 0, wxT("GLCanvas"),  args)
{
	initialized = false;
	m_gllist = 0;
	Connect( wxID_ANY, wxEVT_PAINT,
		(wxObjectEventFunction) &bmdGLCanvas::OnPaint );
	Connect( wxID_ANY, wxEVT_ERASE_BACKGROUND,
		(wxObjectEventFunction) &bmdGLCanvas::OnEraseBackground );
	Connect( wxID_ANY, wxEVT_SIZE,
		(wxObjectEventFunction) &bmdGLCanvas::OnSize); 

}

void bmdGLCanvas::OnSize(wxSizeEvent& event)
{
    // this is also necessary to update the context on some platforms
    wxGLCanvas::OnSize(event);
    //ResetProjectionMode();
}

void bmdGLCanvas::OnEraseBackground(wxEraseEvent& WXUNUSED(event))
{
    // nothing - apparently prevents flicker on Windows
}

void bmdGLCanvas::InitGL()
{
	SetCurrent();

	glShadeModel(GL_SMOOTH);						// Enable Smooth Shading
	glClearColor(backR, backG, backB, 0.5f);					// Black Background
	glClearDepth(1.0f);							// Depth Buffer Setup
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);					// Set Line Antialiasing
	glEnable(GL_BLEND);							// Enable Blending
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);			// Type Of Blending To Use
	LoadTextures();

}

void bmdGLCanvas::OnPaint( wxPaintEvent& WXUNUSED(event) )
{
	wxPaintDC dc(this); //program freezes if this is not used

    //this->SetCurrent();
	SetCurrent();

	if(!initialized)
	{
		InitGL();
		initialized = true;
	}



    /* clear color and depth buffers */
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	glClearColor (backR, backG, backB, 0.5f);

	glLineWidth(1.0f);
	//glDisable(GL_LINE_SMOOTH);
	glLoadIdentity();

	float resH = 640.0, resV = 480.0;
	unsigned int nHmin = 0, nVmin = 0; 
	unsigned int nHmax = 24, nVmax = 18; // < 48
	double dH, dV;

	nHmax = gc.engine.getWidth();
	nVmax = gc.engine.getHeight();

	if((nHmax - nHmin)*resV >= (nVmax - nVmin)*resH)
	{
		dH = 2.0/ (nHmax - nHmin);
		dV = dH * resH / resV;
	}
	else
	{
		dV = 2.0 / (nVmax - nVmin);
		dH = dV * resV / resH;
	}

	/*
	glColor3f(1.0f,0.0f,0.0f);
	glBegin(GL_LINES);
	for(unsigned int i = nHmin; i < nHmax + 1; i++) //+1 so a true border is shown
	{
			glVertex2d(i*dH - 1.0, -1.0);
			glVertex2d(i*dH - 1.0, 1.0);
	}

	//glColor3f(0.0f,1.0f,0.0f);
	for(unsigned int i = nVmin; i < nVmax + 1; i++) //+1 so a true border is shown
	{
			glVertex2d(-1.0 , flip(i*dV - 1.0));
			glVertex2d(1.0 , flip(i*dV - 1.0));
	}
	glEnd();
	*/

	//glEnable(GL_ALPHA_TEST);
	//glAlphaFunc(GL_GREATER,0.1f);

	


	double ti, tj;
	int temp_d;
	vector<PowerUp>::iterator powerup;
	
	glEnable(GL_TEXTURE_2D);				// Enable Texture Mapping

	glColor3f(1.0f,1.0f,1.0f);				// Bright White Color


	glBindTexture(GL_TEXTURE_2D, texture[texPupBOMB]);		// select Pbomb (powerup) image
	glBegin(GL_QUADS);			// Draw A Textured Quad
	powerup = gc.engine.powerups.begin();
	for(; powerup != gc.engine.powerups.end(); powerup++)
	{
		if(powerup->power_type_index == pBOMBS)
		{
			ti = (powerup->coords.x)*dH - 1.0;
			tj = (powerup->coords.y)*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
	}
	glEnd();				// Done Texturing The Box

	glBindTexture(GL_TEXTURE_2D, texture[texPupSPEED]);		// select Pspeed (powerup) image
	glBegin(GL_QUADS);			// Draw A Textured Quad
	powerup = gc.engine.powerups.begin();
	for(; powerup != gc.engine.powerups.end(); powerup++)
	{
		if(powerup->power_type_index == pSPEED)
		{
			ti = (powerup->coords.x)*dH - 1.0;
			tj = (powerup->coords.y)*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
	}
	glEnd();				// Done Texturing The Box

	glBindTexture(GL_TEXTURE_2D, texture[texPupPOWER]);		// select Ppower (powerup) image
	glBegin(GL_QUADS);			// Draw A Textured Quad
	powerup = gc.engine.powerups.begin();
	for(; powerup != gc.engine.powerups.end(); powerup++)
	{
		if(powerup->power_type_index == pPOWER)
		{
			ti = (powerup->coords.x)*dH - 1.0;
			tj = (powerup->coords.y)*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
	}
	glEnd();				// Done Texturing The Box

	glBindTexture(GL_TEXTURE_2D, texture[texPupKICK]);		// select Pkicking (powerup) image
	glBegin(GL_QUADS);			// Draw A Textured Quad
	powerup = gc.engine.powerups.begin();
	for(; powerup != gc.engine.powerups.end(); powerup++)
	{
		if(powerup->power_type_index == pKICKING)
		{
			ti = (powerup->coords.x)*dH - 1.0;
			tj = (powerup->coords.y)*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
	}
	glEnd();				// Done Texturing The Box

	glBindTexture(GL_TEXTURE_2D, texture[texPupDETO]);		// select Pdeto (powerup) image
	glBegin(GL_QUADS);			// Draw A Textured Quad
	powerup = gc.engine.powerups.begin();
	for(; powerup != gc.engine.powerups.end(); powerup++)
	{
		if(powerup->power_type_index == pDETONATION)
		{
			ti = (powerup->coords.x)*dH - 1.0;
			tj = (powerup->coords.y)*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
	}
	glEnd();				// Done Texturing The Box









	glBindTexture(GL_TEXTURE_2D, texture[texBOMB]);		// select bomb image
	glBegin(GL_QUADS);			// Draw A Textured Quad
	vector<Bomb>::iterator bomb = gc.engine.bombs.begin();
	for(bomb; bomb != gc.engine.bombs.end(); bomb++)
	{
		ti = (bomb->coords.x + bomb->mstate.move_fracX())*dH - 1.0;
		tj = (bomb->coords.y + bomb->mstate.move_fracY())*dV - 1.0;
		draw_texture(ti,tj,dH,dV);
	}
	glEnd();				// Done Texturing The Box


	//glBindTexture(GL_TEXTURE_2D, texture[texBOMBER_N]);		// select bomber image

	vector<Bomber>::iterator bomber = gc.engine.bombers.begin();
	for(bomber; bomber != gc.engine.bombers.end(); bomber++)
	{
		if(bomber->is_alive())
		{
			temp_d = bomber->mstate.get_direction();
			if(temp_d == dNORTH)
				glBindTexture(GL_TEXTURE_2D, texture[texBOMBER_N]);
			else if(temp_d == dEAST)
				glBindTexture(GL_TEXTURE_2D, texture[texBOMBER_E]);
			else if(temp_d == dWEST)
				glBindTexture(GL_TEXTURE_2D, texture[texBOMBER_W]);
			else //dSOUTH or dVOID both face south
				glBindTexture(GL_TEXTURE_2D, texture[texBOMBER_S]);

			glBegin(GL_QUADS);			// Draw A Textured Quad

			ti = (bomber->coords.x + bomber->mstate.move_fracX())*dH - 1.0;
			tj = (bomber->coords.y + bomber->mstate.move_fracY())*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
			glEnd();				// Done Texturing The Box
		}
	}

	
					// Disable Texture Mapping


	glBindTexture(GL_TEXTURE_2D, texture[texBLOCK]);		// select block image
	glBegin(GL_QUADS);			// Draw A Textured Quad

	for(unsigned int i = nHmin; i < nHmax; i++)
		for(unsigned int j = nVmin; j < nVmax; j++)
		{
			if(gc.engine.check_tile_index(i,j,2)) //2 is the tile index for blocks
			{
				ti = i*dH - 1.0;
				tj = j*dV - 1.0;
				draw_texture(ti,tj,dH,dV);
			}
		}
	glEnd();
	//glDisable(GL_TEXTURE_2D);				// Disable Texture Mapping

	glBindTexture(GL_TEXTURE_2D, texture[texBRICK]);		// select brick image
	glBegin(GL_QUADS);			// Draw A Textured Quad

	for(unsigned int i = nHmin; i < nHmax; i++)
		for(unsigned int j = nVmin; j < nVmax; j++)
		{
			if(gc.engine.check_tile_index(i,j,1)) //1 is the tile index for blocks
			{
				ti = i*dH - 1.0;
				tj = j*dV - 1.0;
				draw_texture(ti,tj,dH,dV);
			}
		}
	glEnd();


	glBindTexture(GL_TEXTURE_2D, texture[texEXPLOSION_BRICK]);		// select explosion brick image
	glBegin(GL_QUADS);			// Draw A Textured Quad

	for(unsigned int i = nHmin; i < nHmax; i++)
		for(unsigned int j = nVmin; j < nVmax; j++)
		{
			if(gc.engine.check_tile_index(i,j,tEXPLOSION_BRICK)) //1 is the tile index for blocks
			{
				ti = i*dH - 1.0;
				tj = j*dV - 1.0;
				draw_texture(ti,tj,dH,dV);
			}
		}
	glEnd();


	glBindTexture(GL_TEXTURE_2D, texture[texEXPLOSION]);		// select explosion image
	glBegin(GL_QUADS);			// Draw A Textured Quad

	vector<Explosion>::iterator explosion = gc.engine.explosions.begin();
	for(explosion; explosion != gc.engine.explosions.end(); explosion++)
	{
		ti = explosion->explosion_data.center.x*dH - 1.0;
		tj = explosion->explosion_data.center.y*dV - 1.0;
		draw_texture(ti,tj,dH,dV);


		for(unsigned int i = 1; i <= explosion->explosion_data.dW; i++)
		{
			ti = (explosion->explosion_data.center.x - i)*dH - 1.0;
			tj = explosion->explosion_data.center.y*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
		for(unsigned int i = 1; i <= explosion->explosion_data.dE; i++)
		{
			ti = (explosion->explosion_data.center.x + i)*dH - 1.0;
			tj = explosion->explosion_data.center.y*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
		for(unsigned int i = 1; i <= explosion->explosion_data.dN; i++)
		{
			ti = explosion->explosion_data.center.x*dH - 1.0;
			tj = (explosion->explosion_data.center.y - i)*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
		for(unsigned int i = 1; i <= explosion->explosion_data.dS; i++)
		{
			ti = explosion->explosion_data.center.x*dH - 1.0;
			tj = (explosion->explosion_data.center.y + i)*dV - 1.0;
			draw_texture(ti,tj,dH,dV);
		}
	}
	glEnd();




	glDisable(GL_TEXTURE_2D);				// Disable Texture Mapping


    glFlush();
    SwapBuffers();
}




//constants for button IDs
const int bID_QUIT = 1;
const int bID_STEP = 2;
const int bID_LEFT = 3;
const int bID_RIGHT = 4;
const int bID_UP = 5;
const int bID_DOWN = 6;
const int bID_PLACE = 7;
const int bID_DETO = 8;
const int bID_TCONTROL = 9;
const int bID_FASTER = 10;
const int bID_SLOWER = 11;





/*
Most of the important initialization occurs in this function.
The frame is initialized.
The background color is set.
The consitutuent menu, canvas and buttons are initialized.
Events are connected.
The timer is initialized but not started.
*/
MasterFrame::MasterFrame(const wxString& title)
       : wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, wxSize(780, 480))
{
	gc.init();

	wxColor temp_color(defR,defG,defB);
	this->SetBackgroundColour(temp_color);
	

	menubar = new wxMenuBar;
	file = new wxMenu;

	file->Append(wxID_CLOSE, wxT("&Quit"));
	menubar->Append(file, wxT("&File"));
	SetMenuBar(menubar);

	//we can get his from defR,defB,defG
	//however if the change is rejected for any reason
	//this will make it look slightly better
	temp_color = this->GetBackgroundColour();
	backR = (float)temp_color.Red() / 255.0;
	backG = (float)temp_color.Green() / 255.0;
	backB = (float)temp_color.Blue() / 255.0;

	int args[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0};
	gl_canvas = new bmdGLCanvas(this, args);
	gl_canvas->Show();

	quit_button = new wxButton(this, bID_QUIT, wxString("Quit"),wxPoint(660,30));
	quit_button->Show();

	step_button = new wxButton(this, bID_STEP, wxString("Step Once"),wxPoint(660, 90));
	step_button->Show();

	tcontrol_button = new wxButton(this, bID_TCONTROL, wxString("START"),wxPoint(660, 300));
	tcontrol_button->Show();

	faster_button = new wxButton(this, bID_FASTER, wxString("Step Faster"),wxPoint(660, 330));
	faster_button->Show();

	slower_button = new wxButton(this, bID_SLOWER, wxString("Step Slower"),wxPoint(660, 360));
	slower_button->Show();


	//Connect is the standard way to connect Events with function calls in wxWidgets
	Connect(wxID_CLOSE, wxEVT_COMMAND_MENU_SELECTED,
		wxCommandEventHandler(MasterFrame::OnQuit));
	
	Connect(bID_QUIT, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MasterFrame::OnQuit));
	Connect(bID_STEP, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MasterFrame::OnStep));
	Connect(bID_TCONTROL, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MasterFrame::OnTControl));
	Connect(bID_FASTER, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MasterFrame::OnFaster));
	Connect(bID_SLOWER, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MasterFrame::OnSlower));

	timer = new wxTimer();
	timer->SetOwner(this);
	Connect(wxEVT_TIMER, wxTimerEventHandler(MasterFrame::OnTTick));

	Centre();
}

/*
Below are the event implementations for the commands and timer.
They are very straight forward.
*/
void MasterFrame::OnTTick(wxTimerEvent& WXUNUSED(event))
{
	gc.run_step();
	gl_canvas->Refresh();
}

void MasterFrame::OnTControl(wxCommandEvent& WXUNUSED(event))
{
	if(timer->IsRunning())
	{
		timer->Stop();
		tcontrol_button->SetLabel(wxString("Continue"));
	}
	else
	{
		timer->Start(TIMER_DELAY);
		tcontrol_button->SetLabel(wxString("Pause"));
	}
}

void MasterFrame::OnFaster(wxCommandEvent& WXUNUSED(event))
{
	if(TIMER_DELAY > 1)
		TIMER_DELAY /= 2;
	
	if(timer->IsRunning())
	{
		timer->Stop();
		timer->Start(TIMER_DELAY);
	}	
}

void MasterFrame::OnSlower(wxCommandEvent& WXUNUSED(event))
{
	if(TIMER_DELAY > 1)
		TIMER_DELAY *= 2;
	
	if(timer->IsRunning())
	{
		timer->Stop();
		timer->Start(TIMER_DELAY);
	}
}




void MasterFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
{Close(true);}

void MasterFrame::OnStep(wxCommandEvent& WXUNUSED(event))
{
	gc.run_step();
	gl_canvas->Refresh();}
